/**
 *  @file Random.cpp
 */
#include "Random.h"
#include "../common/CppflibMain.h"
#include "../common/CppMemPool.h"
#include "../common/UtilFuncT.h"
#include "../text/CFString.h"
#include "DateTime.h"
#include "../exceptions/ArgumentException.h"
#include "../exceptions/RuntimeException.h"
#if defined(WIN32)
#   include <windows.h>
#   include <Wincrypt.h>
#   pragma comment(lib, "advapi32.lib")
#elif defined(linux)
#   include <stdio.h>
#endif

using namespace cppflib::exceptions;

namespace cppflib
{

namespace util
{


#if defined(WIN32)

PRIVATE HCRYPTPROV __GetCryptContext(ptr_t h)
{
   return static_cast<HCRYPTPROV>(h);
}

#elif defined(linux)

PRIVATE FILE * __GetFilePtr(ptr_t h)
{
   return reinterpret_cast<FILE*>(h);
}

#endif


Random::Random(void)
{
   handle = static_cast<ptr_t>(NULL);
   InitHandle();
}


Random::~Random(void)
{
   Close();
}

/** 
 *  @param [in] seed -- to initialize the random number generator (not used on Windows)
 */
void Random::InitHandle()
{
#if defined(WIN32)
   if (handle)
      return;

   HCRYPTPROV hCryptContext = NULL;
   if (CryptAcquireContextW(&hCryptContext,  // handle to the CSP
                             NULL,            // container name 
                             NULL,            // use the default provider
                             PROV_RSA_FULL,   // provider type
                             0)) {

      handle = hCryptContext;
      return;
   }

   if (cppflib::GetLastError() == NTE_BAD_KEYSET) { // try once more if this error occurs
      if (CryptAcquireContextW(&hCryptContext, NULL, NULL, PROV_RSA_FULL, CRYPT_NEWKEYSET)) {
         handle = hCryptContext;
         return;
      }
   }
   
#elif defined(linux)
  // read random bytes from /dev/urandom or /dev/random
  FILE *fp = fopen("/dev/urandom", "rb");
  if (!fp) {
     fp = fopen("/dev/random", "rb");
  }

  if (fp) {
     handle = reinterpret_cast<ptr_t>(fp);
     return;
  }
   
#endif

   
   THROW_CTOR_EXCEP(RuntimeException, _S("Error initializing random instance"));
}

void Random::Close()
{
   ReleaseBeforeThrowing();
}

void Random::ReleaseBeforeThrowing()
{
#if defined(WIN32)
   if (handle) {
      CryptReleaseContext(__GetCryptContext(handle), 0);
      handle = static_cast<ptr_t>(NULL);
   }
#elif defined(linux)
   if (handle) {
      fclose(__GetFilePtr(handle));
      handle = static_cast<ptr_t>(NULL);
   }
#endif
}

#ifndef MAXINT32
#   define MAXINT32 0x7fffffff
#endif

/**
 *  Get 32 bit random integer (0 - 0x7fffffff)
 */
int Random::GetInt32()
{
   return GetInt32(0, MAXINT32);
}

/**
 *  Convert bytes in buffer to a u32_t integer
 */
PRIVATE u32_t __Byte2u32(u8_t * pBuf, int nrByte)
{
   u32_t n = 0;
   for (int i = 0; i < nrByte; ++i) {
      n <<= 8;
      n |= (pBuf[i] & 0xff);
   }
   return n;
}

/**
 *  Determine how many bytes are needed based on the 'max' value
 */
PRIVATE int __ByteNeed(i64_t max)
{
   i64_t cmpVal = I64N(0xffffffffffffff);
   for (int i = 0; i < 8; ++i) {
      if (max > cmpVal)
         return 8 - i;

      cmpVal = cmpVal >> 8;
   }

   return 0;
}

PRIVATE void __GetRandomBytes(ptr_t handle, u8_t * pBuf, int nrByte)
{
#if defined(WIN32)      
   if (!CryptGenRandom(__GetCryptContext(handle), nrByte, pBuf)) 
#elif defined(linux)
   if (fread(pBuf, 1, nrByte, __GetFilePtr(handle)) < nrByte) 
#endif
   {
      throw RuntimeException(_S("Error getting random bytes"));
   }
}

/**
 *  Get random integer with a min and a max value (both inclusive)
 */
int Random::GetInt32(int min, int max)
{
   u32_t ret = 0;


   if (min < 0 || static_cast<u32_t>(max) > MAXINT32)
      throw ArgumentException(_S("min < 0 or max > 0x7fffffff"));

   if (min >= max)
      throw ArgumentException(_S("min >= max"));

   u8_t temp[4];
   int nrByte = __ByteNeed(static_cast<i64_t>(max));
   do {
      __GetRandomBytes(handle, temp, nrByte);
      ret = __Byte2u32(temp, nrByte);

      if (ret <= MAXINT32) {
         if (static_cast<int>(ret) >= min && static_cast<int>(ret) <= max)
            break;
      }

   } while(true);

   return static_cast<int>(ret);
}

#ifndef MAXINT64
#   define MAXINT64 I64N(0x7fffffffffffffff)
#endif

/**
 *  Get 64 bit random integer (0 - 0x7fffffffffffffff)
 */
i64_t Random::GetInt64()
{
   return GetInt64(0, MAXINT64);
}

/**
 *  Convert bytes in buffer to a u64_t integer
 */
PRIVATE u64_t __Byte2u64(u8_t * pBuf, int nrByte)
{
   u64_t n = 0;
   for (int i = 0; i < nrByte; ++i) {
      n <<= 8;
      n |= (pBuf[i] & 0xff);
   }
   return n;
}

/**
 *  Get random integer with a min and a max value (both inclusive)
 */
i64_t Random::GetInt64(i64_t min, i64_t max)
{
   u64_t ret = 0;


   if (min < 0 || static_cast<u64_t>(max) > MAXINT64)
      throw ArgumentException(_S("min < 0 or max > 0x7fffffffffffffff"));

   if (min >= max)
      throw ArgumentException(_S("min >= max"));

   u8_t temp[8];
   int nrByte = __ByteNeed(max);
   do {
      __GetRandomBytes(handle, temp, nrByte);
      ret = __Byte2u64(temp, nrByte);

      if (ret <= MAXINT64) {
         if (static_cast<i64_t>(ret) >= min && static_cast<i64_t>(ret) <= max)
            break;
      }

   } while(true);

   return static_cast<i64_t>(ret);
}

/**
 *  Get random bytes
 */
void Random::GetBytes(collections::ArrayByte &buf)
{
   GetBytes(buf, 0, buf.GetSize());
}

/** 
 *  Get random bytes
 *
 *  @param [in, out] buf -- buffer to hold the random bytes
 *  @param [in] offset -- starting offset of buf
 *  @param [in] count -- number of bytes to get
 */
void Random::GetBytes(collections::ArrayByte &buf, int offset, int count)
{
   if (offset < 0 || offset >= static_cast<int>(buf.GetSize()) || count <= 0)
      return;

   int nrByte = static_cast<int>(buf.GetSize()) - offset;
   nrByte = cppflib::Min(count, nrByte);
   u8_t * pBuf = buf.GetRawPtr() + offset;
   __GetRandomBytes(handle, pBuf, nrByte);
}

PRIVATE const wc_t st_charPool[] = _S("0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz");

/**
 *  Find character position in the above array
 */
PRIVATE int __CharPos(const wc_t c)
{
   pcwstr_t pool = st_charPool;

   while (*pool) {
      if (*pool == c)
         return pool - st_charPool;

      ++pool;
   }

   return -1;
}

/**
 *  Get random characters with specified needs
 *
 *  @param [in, out] s -- string to hold the resultant random string
 *  @param [in] type -- type of letters required
 *  @param [in] num -- number of characters needed
 */
void Random::GetString(CFString &str, randtype_t type, int num)
{
   int startPos = 0, endPos = 0;
   switch(type) {
      case RT_HEXDIGIT:
         startPos = __CharPos(_S('0'));
         endPos = __CharPos(_S('F'));
         break;
      case RT_DECDIGIT:
         startPos = __CharPos(_S('0'));
         endPos = __CharPos(_S('9'));
         break;
      case RT_ALPHANUMERIC:
         startPos = __CharPos(_S('0'));
         endPos = __CharPos(_S('Z'));
         break;
      case RT_ALPHANUMERIC_NOCASE:
         startPos = __CharPos(_S('0'));
         endPos = __CharPos(_S('z'));
         break;
      case RT_UPPERCASE:
         startPos = __CharPos(_S('A'));
         endPos = __CharPos(_S('Z'));
         break;
      case RT_LOWERCASE:
         startPos = __CharPos(_S('a'));
         endPos = __CharPos(_S('z'));
         break;
   }

   str = _S("");  // clear first


   int nrCharNeed = num;
   const int POOLSIZE = endPos - startPos + 1;
   const int BUFSIZE = 16;
   u8_t randBytesBuf[BUFSIZE];

   while (nrCharNeed > 0) {
      __GetRandomBytes(handle, randBytesBuf, BUFSIZE);
      
      for (int i = 0; i < BUFSIZE && nrCharNeed > 0; ++i) {
         int pos = static_cast<int>(randBytesBuf[i] & 0xff) % POOLSIZE;
         str += st_charPool[startPos + pos];
         --nrCharNeed;
      }
   }

}



} // end of namespace util

} // end of namespace cppflib
